Faked Fog

by StyX/HeadcrasH

O.K., some friends asked me how I created the fog in our latest demo "Netherworld" and as it's neither a secret nor a stunning trick I'm gonna describe it here. Today most people will probably use glEnable(GL_FOG), but maybe someone needs it for his 64k intro or whatever. It's quite a compact method, so it could be used for such things. On the other hand it's not the fastest one. And don't forget the title, it's heavily faked. But to bring some atmosphere to a 3d-scene it's o.k. And apart from that, faking rulez. =) Enough blabla, let's start.

The idea is not to change any polygon-drawers with fog-code, which makes things terribly slow, because also with hardcore backface-culling and bounding boxes you might not be able to strike out all invisible polygons. One thing you MUST use in your 3d scene is a PERSPECTIVE CORRECT zbuffer, which means not containing z but 1/z. As you can't mix both types, no problem I think. (And when you implement your perspective texture-mapper well, there won't be a big difference to linear mapping anyway. I tried it with partly linear polys and found out that I didn't even see a bit of more speed.) So how does this thing work? Once you have rendered your scene, you take your zbuffer, get each value out of it and calculate a white-value representing the depth of the pixel. Additionally you also have to darken the pixel at this point. You see that this method is not very good for higher resolutions than 320x240 (if you don't have a really powerful machine).

Now let's go for some pseudocode in C. I assume that you internally do 32bit rendering and your zbuffer is a DWORD 1/z array (fixed, the values are all scaled up, only 31 bit used. Example: perspective_z = (1<<31)/z). It might also work with other implementations but then you have to figure it out by yourself. =)

(Remember: pos_red means the red-value of your rgb-buffer at the position pos.)

  int long faraway = 900000000; // this is our "far plane". everything deeper
                                // than this is completely invisible.
                                // depends on the size of your scene and how
                                // far you want to look
                               
  int fog, fogval;              // some auxiliary variables
  int red, green, blue;
  .....

  render your amazing scene here

  .....
  for (int i=0;i<buffersize;i++)
  {
    if (zbuffer[i] > faraway)   // not visible, completely white
    {
       screen[pos_red] =
       screen[pos_green] =
       screen[pos_blue] = 255;  // set r,g,b to 255 (= white...)
    }
    else
    {
       fog = (abs(zbuffer[i])) >> shift; // shift depends on the size of your
                                         // scene and how much fog you like
                                         // I used 16 most of the time

       if (fog>255) fog = 255;           // no overflow boys...

       fogval = 255-fog;                 // base color

       red   = fogval+((fog*screen[pos_red]  )>>8); // new rgb values
       green = fogval+((fog*screen[pos_green])>>8);
       blue  = fogval+((fog*screen[pos_blue] )>>8);
    }
  }

Well, that's it. This formula dims down the color with the same value as it is scaled up to white. The "faraway" and "shift" values have to be adjusted for your purposes. An implementation in assembler would probably speed up things a bit. You can also adjust the intensity of the fog. Therefore multiply "fog" with a 8bit-fixed value and shift it back again. Example:

                        int fogness = 128; // 0.5 * 256 = 128
                        ...
                        fog *= fogness;    // multiply
                        fog >>= 8;         // scale back
                        ...

If fog has been 200, it will be 100 afterwards, because as we know:

                        (200*128) >> 8
                     => (200*128) / 256
                     =  100

Hard math tricks.

Well, adjusting your fog makes things not faster btw., but it can be used for nice things like fog coming up or becoming lighter (I did that in our demo, I wonder if anyone ever noticed that :)).

Now about some limitations of this stuff:

- It's strictly linear from the viewer's position, no matter in which direction you look. (But this is sufficient in most cases.)

- You can only use it for outside-scenes because the fog is everywhere (see a little bug in our demo, for a short time you can see the fog in the corner of a tomb).

- It does not move (ha, you didn't guess that, eh?).

- No colored fog, maybe with some weird code you could do that.

But it also has an advantage: you can also implement far-clipping in this scene. Usually far-clipping sux, I don't use it because no matter how far away you define your far-plane, it always looks crappy when the drawing suddenly stops. But with the fog it's no problem.

An optimizing trick: if your frame is not completely filled (means: you have black spaces, e.g. if you don't use sky-textures), you can clear your screen buffer with 255 instead of 0. Then you can do your far-check for the fog this way:

    if (zbuffer[i] < faraway)
    {
      < do stuff >
    }

and ignore the faraway pixels completely because they are already white. You also can define a "near-plane" where you can't see any fog at all and thus save some more clock cycles...

O.K., happy implementing. If you think this is useful in some way give me feedback. :)

Before I forget it: all this stuff is right from my head, so if you got a better way of doing simple fog (or even real fog :)) I'd like to know something about it, just because I like fog. (To be more exact, I'm obsessed by fog...) Actually I'm dead-sure there are lotsa better ways.

StyX/HeadcrasH

- stuck in fog and not from cologne

Mail me about all kinds of democoding: andi.schilling@gmx.de
Go here for more or less lame productions: http://www.head-crash.de
To locate me at demoparties follow the fog to its source.